package com.dlc.shop.manager.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSON;
import com.github.binarywang.wxpay.service.WxPayService;
import com.dlc.shop.allinpay.OpenClient;
import com.dlc.shop.allinpay.config.AllinpayConfig;
import com.dlc.shop.allinpay.service.AllinpayOrderService;
import com.dlc.shop.bean.enums.UserBalanceLogType;
import com.dlc.shop.bean.event.BalanceRefundEvent;
import com.dlc.shop.bean.event.allinpay.AllinpayBalanceUpdateEvent;
import com.dlc.shop.bean.pay.PayInfoDto;
import com.dlc.shop.bean.pay.RefundInfoDto;
import com.dlc.shop.common.allinpay.constant.AllinpayConstant;
import com.dlc.shop.common.allinpay.constant.AllinpayPlatformAccountSet;
import com.dlc.shop.common.allinpay.constant.PaySysType;
import com.dlc.shop.common.allinpay.member.constant.VisitSourceType;
import com.dlc.shop.common.allinpay.member.resp.BindCard;
import com.dlc.shop.common.allinpay.order.req.*;
import com.dlc.shop.common.allinpay.order.resp.*;
import com.dlc.shop.common.bean.Allinpay;
import com.dlc.shop.common.bean.Domain;
import com.dlc.shop.common.config.Constant;
import com.dlc.shop.common.constants.SegmentIdKey;
import com.dlc.shop.common.enums.PayForm;
import com.dlc.shop.common.enums.PayType;
import com.dlc.shop.common.exception.YamiShopBindException;
import com.dlc.shop.common.response.ResponseEnum;
import com.dlc.shop.common.response.ServerResponseEntity;
import com.dlc.shop.common.util.Arith;
import com.dlc.shop.common.util.BeanUtil;
import com.dlc.shop.common.util.IpHelper;
import com.dlc.shop.config.ShopConfig;
import com.dlc.shop.config.WxConfig;
import com.dlc.shop.service.SysConfigService;
import lombok.AllArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;

import java.text.SimpleDateFormat;
import java.time.LocalTime;
import java.util.*;

/**
 * 统一支付工具
 *
 * @author FrozenWatermelon
 */
@Service
@AllArgsConstructor
public class AllinpayManager {

    private final ApplicationContext applicationContext;

    private final AllinpayConfig allinpayConfig;
    private final WxConfig wxConfig;

    private final AllinpayOrderService allinpayOrderService;
    private final SysConfigService sysConfigService;
    private final ShopConfig shopConfig;

    private static final String WX_SUCCESS_XML = "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";
    private static final String ALI_SUCCESS = "success";
    public static final String SUCCESS = "SUCCESS";
    private static final String TRADE_SUCCESS = "TRADE_SUCCESS";
    private static final String TRADE_STATUS_KEY = "trade_status";
    private static final String REFUND = "refund_fee";
    private static final String ALLINPAY = "allinpay";


    private static final Logger logger = LoggerFactory.getLogger(AllinpayManager.class);

    public ServerResponseEntity<?> doAllinPay(PayInfoDto payInfo) {
        if (payInfo.getPayAmount() > Constant.MAX_WECHA_TPAY_AMOUNT) {
            throw new YamiShopBindException("已超出微信支付的最大金额，请选择其他付款方式");
        }
        PayType payType = PayType.instance(payInfo.getPayType());
        if (Objects.isNull(payType)) {
            throw new YamiShopBindException("选择的支付方式不存在，请重新选择");
        }
        if (Objects.isNull(payInfo.getValidateType())) {
            payInfo.setValidateType(0L);
            if (Objects.equals(payType, PayType.BALANCE)) {
                payInfo.setValidateType(2L);
            }
        }
        // 充值申请
        if (Objects.equals(PayForm.RECHARGE_BALANCE.getValue(), payInfo.getPayForm())) {
            DepositApplyResp resp = this.depositApply(payInfo);
            return ServerResponseEntity.success(resp);
        }
        // 消费申请（平台会员购买）
        if (Objects.equals(PayForm.BUY_VIP.getValue(), payInfo.getPayForm())) {
            ConsumeApplyResp resp = this.consumeApply(payInfo);
            return ServerResponseEntity.success(resp);
        }
        // 需要支付金额的积分商品购买
        if (CollectionUtil.isEmpty(payInfo.getReceiveList())) {
            ConsumeApplyResp resp = this.payScoreOrder(payInfo);
            return ServerResponseEntity.success(resp);
        }

        ApplicationCollection applicationCollection = new ApplicationCollection();
        applicationCollection.setBizOrderNo(payInfo.getPayNo());
        applicationCollection.setPayerId(AllinpayConstant.USER + payInfo.getUserId());
        List<ApplicationCollection.Reciever> recieverList = BeanUtil.mapAsList(payInfo.getReceiveList(), ApplicationCollection.Reciever.class);
        applicationCollection.setRecieverList(recieverList);
        applicationCollection.setTradeCode("3001");
        applicationCollection.setAmount(Arith.toLongCent(payInfo.getPayAmount()));
        applicationCollection.setFee(0L);
        applicationCollection.setBackUrl(payInfo.getApiNoticeUrl());
        applicationCollection.setFrontUrl(payInfo.getReturnUrl());
        applicationCollection.setOrderExpireDatetime(DateUtil.offsetMinute(new Date(), Constant.ORDER_MAX_TIME).toString());
        applicationCollection.setIndustryCode("2522");
        applicationCollection.setIndustryName("其他");
        applicationCollection.setSource(payInfo.getSystemType());
        applicationCollection.setExtendInfo(payInfo.getExtendInfo());
        applicationCollection.setValidateType(payInfo.getValidateType());
        return doPay(applicationCollection, payInfo);
    }


    private ServerResponseEntity<?> doPay(ApplicationCollection applicationCollection, PayInfoDto payInfo) {
        payInfo.setPaySysType(PaySysType.ALLINPAY.value());
        Allinpay allinpay = sysConfigService.getSysConfigObject(Constant.ALLINPAY_CONFIG, Allinpay.class);
        applicationCollection.setPayMethod(buildPayMethod(payInfo, allinpay));
        ApplicationCollectionResp applicationCollectionResp = allinpayOrderService.agentCollectApply(applicationCollection);
        // 扫码支付信息/ JS支付串信息（微信、支付宝、QQ钱包）/微信小程序/微信原生H5支付串信息/支付宝原生APP支付串信息
        // 微信小程序，公众号，扫码，h5,以及支付宝电脑,手机网站都是返回payInfo
        return ServerResponseEntity.success(applicationCollectionResp);
    }

    public boolean isWxPay(PayType payType) {
        return (Objects.equals(payType, PayType.WECHATPAY)
                || Objects.equals(payType, PayType.WECHATPAY_SWEEP_CODE)
                || Objects.equals(payType, PayType.WECHATPAY_H5)
                || Objects.equals(payType, PayType.WECHATPAY_MP)
                || Objects.equals(payType, PayType.WECHATPAY_APP));
    }

    public boolean isAliPay(PayType payType) {
        return (Objects.equals(payType, PayType.ALIPAY_H5)
                || Objects.equals(payType, PayType.ALIPAY)
                || Objects.equals(payType, PayType.ALIPAY_APP));
    }

    public boolean batchShareAccountToShop(BatchAgentPay batchAgentPay) {
        try {
            BatchAgentPayResp agentPayResp = allinpayOrderService.batchAgentPay(batchAgentPay);
        } catch (Exception e) {
            logger.error("结算失败,结算信息异常：", e);
            return false;
        }
        return true;
    }

    public boolean doRefund(RefundInfoDto refundInfo) {
        if (Objects.equals(refundInfo.getRefundAmount(), 0.0)) {
            return true;
        }
        Domain domain = shopConfig.getDomain();
        Refund refund = new Refund();
        refund.setBizOrderNo(SegmentIdKey.REFUND + refundInfo.getRefundSn());
        refund.setOriBizOrderNo(refundInfo.getPayNo());
        // 退款收款人，必须是原订单中的付款方
        refund.setBizUserId(AllinpayConstant.USER + refundInfo.getUserId());
        refund.setRefundType("D0");
        refund.setBackUrl(domain.getMultishopDomainName() + refundInfo.getNotifyUrl());
        refund.setAmount(Arith.toLongCent(refundInfo.getRefundAmount()));
        // bizUserId不为空时，需要退款账户集编号
        if (Objects.nonNull(refundInfo.getBizUserId())) {
            // 托管代收订单中的收款人的退款金额，通常是商家
            List<Refund.RefundAccountInfo> refundList = new ArrayList<>(1);
            Refund.RefundAccountInfo refundAccountInfo = new Refund.RefundAccountInfo();
            refundAccountInfo.setBizUserId(AllinpayConstant.SHOP + refundInfo.getBizUserId());
            refundAccountInfo.setAmount(Arith.toLongCent(refundInfo.getRefundAmount()));
            refundList.add(refundAccountInfo);
            refund.setRefundList(refundList);
        }
        try {
            RefundResp agentPayResp = allinpayOrderService.doRefund(refund);
            // 如果是余额支付回退余额
            if (Objects.equals(refundInfo.getPayType(), PayType.BALANCE)) {
                applicationContext.publishEvent(new BalanceRefundEvent(refundInfo));
            }
        } catch (Exception e) {
            logger.error("退款失败：{}", refundInfo.getRefundSn());
            // 因为这个退款是mq调用的，所以如果一次退款失败，就调用多次，做好幂等的操作
            throw new YamiShopBindException(ResponseEnum.EXCEPTION);
        }
        return false;
    }

    public boolean execApplicationTransfer(String payId, String bizUserId, Long payAmount) {
        if (Objects.equals(payAmount, 0L)) {
            return true;
        }
        Allinpay allinpay = sysConfigService.getSysConfigObject(Constant.ALLINPAY_CONFIG, Allinpay.class);
        if (Objects.isNull(allinpay)) {
            logger.error("平台转账失败, 配置不存在");
            return false;
        }
        ApplicationTransfer applicationTransfer = new ApplicationTransfer();
        applicationTransfer.setBizTransferNo(payId);
        // 标准余额账户集
        applicationTransfer.setSourceAccountSetNo(AllinpayPlatformAccountSet.STANDARD_BALANCE.value().toString());
        applicationTransfer.setTargetBizUserId(bizUserId);
        applicationTransfer.setAmount(payAmount);
        applicationTransfer.setTargetAccountSetNo(allinpay.getAccountSetNo());
        try {
            ApplicationTransferResp applicationTransferResp = allinpayOrderService.applicationTransfer(applicationTransfer);
            return true;
        } catch (Exception e) {
            logger.error("平台转账失败：{}", e.getMessage());
            logger.error("异常：", e);
        }
        return false;
    }

    /**
     * 提现申请
     * 通商云发送并验证短信验证码，有效期3分钟。
     */
    public WithdrawApplyResp withdrawApply(Long payId, String bizUserId, Double amount, String backUrl, BindCard bindCard, Integer source, Long validateType) {
        Allinpay allinpay = sysConfigService.getSysConfigObject(Constant.ALLINPAY_CONFIG, Allinpay.class);
        if (Objects.isNull(allinpay)) {
            throw new YamiShopBindException("提现失败, 配置不存在");
        }
        // 提现校验
        this.checkAllowWithdraw();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.HOUR, 23);
        Date date = calendar.getTime();
        String orderExpireDatetime = sdf.format(date);
        OpenClient client = allinpayConfig.getClient();
        WithdrawApplyReq withdrawApplyReq = new WithdrawApplyReq();
        withdrawApplyReq.setBizOrderNo(payId.toString());
        withdrawApplyReq.setBizUserId(bizUserId);
        withdrawApplyReq.setAccountSetNo(allinpay.getAccountSetNo());
        withdrawApplyReq.setAmount(Arith.toLongCent(amount));
        withdrawApplyReq.setFee(0L);
        withdrawApplyReq.setValidateType(validateType);
        withdrawApplyReq.setBackUrl(backUrl);
        withdrawApplyReq.setOrderExpireDatetime(orderExpireDatetime);
        withdrawApplyReq.setBankCardNo(client.encrypt(bindCard.getBankCardNo()));
        withdrawApplyReq.setBankCardPro(bindCard.getBankCardPro());
        withdrawApplyReq.setIndustryCode(AllinpayConstant.INDUSTRY_CODE);
        withdrawApplyReq.setIndustryName(AllinpayConstant.INDUSTRY_NAME);
        withdrawApplyReq.setSource(source);
        WithdrawApplyResp withdrawApplyResp;
        try {
            withdrawApplyResp = allinpayOrderService.withdrawApply(withdrawApplyReq);
        } catch (Exception e) {
            throw new YamiShopBindException("提现失败：" + e.getMessage());
        }
        return withdrawApplyResp;
    }

    public PayByBackSmsResp payByBackSms(PayByBackSmsReq payByBackSmsReq) {
        if (Objects.isNull(payByBackSmsReq.getVerificationCode())) {
            throw new YamiShopBindException("短信验证码不能为空");
        }
        payByBackSmsReq.setConsumerIp(IpHelper.getIpAddr());
        PayByBackSmsResp payByBackSmsResp;
        try {
            payByBackSmsResp = allinpayOrderService.payByBackSms(payByBackSmsReq);
        } catch (Exception e) {
            throw new YamiShopBindException("确认支付失败，{}", e.getMessage());
        }
        return payByBackSmsResp;
    }

    /**
     * 构建支付方式
     * @param payInfo 支付信息
     * @return 通联payMethod
     */
    public Map<String, Object> buildPayMethod(PayInfoDto payInfo, Allinpay allinpay) {
        PayType payType = PayType.instance(payInfo.getPayType());
        if (Objects.isNull(payType)) {
            throw new YamiShopBindException("选择的支付方式不存在，请重新选择");
        }
        // 补充基础支付方式参数
        Long payAmount = Arith.toLongCent(payInfo.getPayAmount());
        PayMethod payMethod = new PayMethod();
        payMethod.setLimitPay("no_credit");
        payMethod.setAmount(payAmount);
        payMethod.setVspCusid(allinpay.getVspCusId());
        // 微信APP支付 or 微信小程序支付 or 微信公众号支付
        if (Objects.equals(payType, PayType.WECHATPAY_APP)
                || Objects.equals(payType, PayType.WECHATPAY)
                || Objects.equals(payType, PayType.WECHATPAY_MP)) {
            payMethod.setAcct(payInfo.getBizUserId());
            WxPayService wxPayService = wxConfig.getWxPayService(payType);
            payMethod.setSubAppid(wxPayService.getConfig().getAppId());
        }
        // 余额支付
        else if (Objects.equals(payType, PayType.BALANCE)) {
            List<PayMethod.BalancePay> balancePayList = new ArrayList<>(1);
            PayMethod.BalancePay balancePay = new PayMethod.BalancePay();
            balancePay.setAmount(payAmount);
            balancePay.setAccountSetNo(allinpay.getAccountSetNo());
            balancePayList.add(balancePay);
            payMethod.setBalancePayList(balancePayList);
            // 并创建余额支付记录
            this.addBalanceLog(payInfo);
        }
        // 数据封装返回（余额支付单独处理）
        Map<String, Object> payMethodMap = new HashMap<>(1);
        if (Objects.equals(payType, PayType.BALANCE)) {
            payMethodMap.put(payType.allinpayCode(), payMethod.getBalancePayList());
        } else {
            payMethodMap.put(payType.allinpayCode(), payMethod);
        }
        return payMethodMap;
    }

    /**
     * 充值申请
     * @param payInfo
     * @return
     */
    public DepositApplyResp depositApply(PayInfoDto payInfo) {
        Allinpay allinpay = sysConfigService.getSysConfigObject(Constant.ALLINPAY_CONFIG, Allinpay.class);
        if (Objects.isNull(allinpay)) {
            throw new YamiShopBindException("充值失败，配置不存在");
        }
        // 参数准备
        Map<String, Object> payMethodMap = this.buildPayMethod(payInfo, allinpay);
        // 构建参数（手续费暂定为0）
        DepositApply depositApply = new DepositApply();
        depositApply.setBizUserId(payInfo.getAllinpayUserId());
        depositApply.setBizOrderNo(payInfo.getPayNo());
        depositApply.setAmount(Arith.toLongCent(payInfo.getPayAmount()));
        depositApply.setFee(0L);
        if (Objects.nonNull(payInfo.getValidateType())) {
            depositApply.setValidateType(payInfo.getValidateType());
        }
        depositApply.setSource(VisitSourceType.PC.value());
        depositApply.setPayMethod(JSON.toJSONString(payMethodMap));
        depositApply.setIndustryCode(AllinpayConstant.INDUSTRY_CODE);
        depositApply.setIndustryName(AllinpayConstant.INDUSTRY_NAME);
        depositApply.setFrontUrl(payInfo.getReturnUrl());
        depositApply.setBackUrl(payInfo.getApiNoticeUrl());
        depositApply.setAccountSetNo(allinpay.getAccountSetNo());
        // 发送请求，并返回结果
        return allinpayOrderService.depositApply(depositApply);
    }

    /**
     * 消费申请
     * @param payInfoBO
     * @return
     */
    public ConsumeApplyResp consumeApply(PayInfoDto payInfoBO) {
        Allinpay allinpay = sysConfigService.getSysConfigObject(Constant.ALLINPAY_CONFIG, Allinpay.class);
        if (Objects.isNull(allinpay)) {
            throw new YamiShopBindException("消费申请失败，配置不存在");
        }
        ConsumeApply consumeApply = new ConsumeApply();
        Map<String, Object> payMethod = this.buildPayMethod(payInfoBO, allinpay);
        consumeApply.setPayMethod(JSON.toJSONString(payMethod));
        consumeApply.setValidateType(payInfoBO.getValidateType());
        consumeApply.setAmount(Arith.toLongCent(payInfoBO.getPayAmount()));
        consumeApply.setBizOrderNo(payInfoBO.getPayNo());
        consumeApply.setFee(0L);
        consumeApply.setFrontUrl(payInfoBO.getReturnUrl());
        consumeApply.setBackUrl(payInfoBO.getApiNoticeUrl());
        consumeApply.setSource(payInfoBO.getSystemType());
        consumeApply.setIndustryCode(AllinpayConstant.INDUSTRY_CODE);
        consumeApply.setIndustryName(AllinpayConstant.INDUSTRY_NAME);
        consumeApply.setPayerId(payInfoBO.getAllinpayUserId());
        // 若是购买会员，收款方为平台
        if (Objects.equals(PayForm.BUY_VIP.getValue(), payInfoBO.getPayForm())) {
            consumeApply.setRecieverId("#yunBizUserId_B2C#");
        }
        return allinpayOrderService.consumeApply(consumeApply);
    }

    /**
     * 获取订单支付状态
     * @param bizOrderNo
     * @return
     */
    public OrderStatusResp getOrderStatus(String bizOrderNo) {
        GetOrderStatus getOrderStatus = new GetOrderStatus();
        getOrderStatus.setBizOrderNo(bizOrderNo);
        return allinpayOrderService.getOrderStatus(getOrderStatus);
    }

    /**
     * 需要支付金额的积分商品购买
     * @param payInfoBO
     * @return
     */
    public ConsumeApplyResp payScoreOrder(PayInfoDto payInfoBO) {
        Allinpay allinpay = sysConfigService.getSysConfigObject(Constant.ALLINPAY_CONFIG, Allinpay.class);
        if (Objects.isNull(allinpay)) {
            throw new YamiShopBindException("支付失败，配置不存在");
        }
        ConsumeApply consumeApply = new ConsumeApply();
        Map<String, Object> payMethod = this.buildPayMethod(payInfoBO, allinpay);
        consumeApply.setPayMethod(JSON.toJSONString(payMethod));
        consumeApply.setValidateType(payInfoBO.getValidateType());
        consumeApply.setAmount(Arith.toLongCent(payInfoBO.getPayAmount()));
        consumeApply.setBizOrderNo(payInfoBO.getPayNo());
        consumeApply.setFee(0L);
        consumeApply.setFrontUrl(payInfoBO.getReturnUrl());
        consumeApply.setBackUrl(payInfoBO.getApiNoticeUrl());
        consumeApply.setSource(payInfoBO.getSystemType());
        consumeApply.setIndustryCode(AllinpayConstant.INDUSTRY_CODE);
        consumeApply.setIndustryName(AllinpayConstant.INDUSTRY_NAME);
        consumeApply.setPayerId(AllinpayConstant.USER + payInfoBO.getUserId());
        consumeApply.setRecieverId("#yunBizUserId_B2C#");
        return allinpayOrderService.consumeApply(consumeApply);
    }

    /**
     * 创建余额支付记录
     * @param payInfo
     */
    private void addBalanceLog(PayInfoDto payInfo) {
        AllinpayBalanceUpdateEvent event = new AllinpayBalanceUpdateEvent();
        event.setChangeBalance(-payInfo.getPayAmount());
        event.setPayNo(payInfo.getPayNo());
        event.setIsUpdateBalance(false);
        PayForm payForm = PayForm.instance(payInfo.getPayForm());
        if (Objects.nonNull(payForm)) {
            UserBalanceLogType balanceLogType = switch (payForm) {
                case BUY_PRODUCT -> UserBalanceLogType.PAY;
                case BUY_VIP -> UserBalanceLogType.BUY_MEMBER;
                case RECHARGE_BALANCE -> UserBalanceLogType.RECHARGE;
            };
            event.setType(balanceLogType.value());
        } else {
            event.setType(UserBalanceLogType.PAY.value());
        }
        applicationContext.publishEvent(event);
    }

    /**
     */
    private void checkAllowWithdraw() {
        // 提现时间检查
        LocalTime now = LocalTime.now();
        boolean isOutOfTime = AllinpayConstant.ALLOW_WITHDRAW_START_TIME.isAfter(now)
                || AllinpayConstant.ALLOW_WITHDRAW_END_TIME.isBefore(now);
        if (isOutOfTime) {
            throw new YamiShopBindException(String.format("允许提现的时间为：%s - %s", AllinpayConstant.ALLOW_WITHDRAW_START_TIME, AllinpayConstant.ALLOW_WITHDRAW_END_TIME));
        }
    }
}
